#ifndef __COIDSERVER_FMTSOAPXML_H__
#define __COIDSERVER_FMTSOAPXML_H__

#include <coid/comm/binstream/binstreambuf.h>
#include <coid/comm/metastream/fmtstreamxml2.h>
#include <coid/comm/hash/hashmap.h>

using namespace coid;

struct soap_exception
{
	soap_exception(const token m) : _msg(m) {}

	coid::charstr _msg;

	const coid::charstr & error_desc() const { return _msg; }
};

class fmtsoapxml : public fmtstreamxml2
{
	typedef coid::hash_map<coid::charstr, coid::charstr, coid::hash<coid::token> > tattr_map;

	coid::charstr _method_name;
	int _keytbl;
	int _sessionid;

	enum EKey {
		ENVELOPE,
		XMLNS,
		BODY,
		HEADER,

		END_IN_KEY=0x0001,
		CLOSING_KEY=0x0002,
	};

	static const coid::token & keytable(const int t,const EKey k)
	{
		static const coid::token _kt0[]={
			"SOAP-ENV:Envelope",
			"xmlns:SOAP-ENV",
			"SOAP-ENV:Body",
			"SOAP-ENV:Header",
		};
		static const coid::token _kt1[]={
			"soap:Envelope",
			"xmlns:soap",
			"soap:Body",
			"soap:Header",
		};
		return t!=0?_kt1[k]:_kt0[k];
	}

	void parse_attributes(tattr_map & map)
	{
		coid::token t;
		coid::charstr attr, val;

		for(;;) {
			if (!_tokenizer.matches(lexid)) break;
			attr = _tokenizer.last();
			_tokenizer.match('=');
			val = _tokenizer.match_either(lexstr,lexchr);
			map.insert_key_value(attr, val);
		}
	}

	int find_end_of_key()
	{
		static const coid::token KEY_END="/>";
		coid::token tok;

		do {
			tok=_tokenizer.next();
			if( _tokenizer.follows('/') ) {
				if( !_tokenizer.match(KEY_END) ) {
					_tokenizer.prepare_exception() << "Bad XML structure";
                    _tokenizer.append_exception_location();
					throw _tokenizer.exception();
				} else
					return END_IN_KEY;
			}
		} while( tok!='>' );
		return 0;
	}

	void match_val(const coid::token val)
	{
		if (_tokenizer.match_either(lexstr, lexchr) != val) {
			_tokenizer.prepare_exception() << "error parsing SOAP envelope";
			_tokenizer.append_exception_location();
			throw _tokenizer.exception();
		}
	}

	void match_simple_key(const coid::token t)
	{
		_tokenizer.match('<');
		_tokenizer.match(t);
		_tokenizer.match_optional('/');
		_tokenizer.match('>');
	}

	coid::token get_simple_key()
	{
		coid::token tok;

		_tokenizer.match('<');
		tok = _tokenizer.next();
		_tokenizer.match_optional('/');
		_tokenizer.match('>');

		return tok;
	}

	void get_key(coid::charstr & key,tattr_map &attrs,int &flags)
	{
		attrs.clear();
		_tokenizer.match('<');
		if( _tokenizer.match_optional('/') )
			flags=CLOSING_KEY;
		key=_tokenizer.next();
		parse_attributes(attrs);
		flags=find_end_of_key();
	}


	void match_end_key(const coid::token t)
	{
		_tokenizer.match('<');
		_tokenizer.match('/');
		_tokenizer.match(t);
		_tokenizer.match('>');
	}

	///Override to parse custom trailer
	virtual opcd on_read_open() 
	{
		return 0;
	}

	///Override to parse custom trailer
	virtual opcd on_read_close() 
	{
		match_end_key(_method_name);
		match_end_key(keytable(_keytbl,BODY));
		match_end_key(keytable(_keytbl,ENVELOPE));
		return 0;
	}

protected:
	coid::binstream *_bso;


public:

	opcd begin_soap_envelope(coid::binstreambuf *po=0)
	{
		DASSERT(_binw!=0 && "ouput binstream is mandatory!");

		coid::binstream *bso=po!=0?po:_binw;

		static const coid::token TOK_ENVELOPE = "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>";
		static const coid::token TOK_BODY = "<soap:Body>";
		static const coid::token TOK_HEADER_BEGIN = "<soap:Header>";
		static const coid::token TOK_HEADER_END = "</soap:Header>";
		static const coid::token TOK_SESSION = "<Session xmlns='urn:schemas-microsoft-com:xml-analysis' SessionId='213'/>";
		static const coid::token TOK_SESSION_END = "</Session>";

		// xmlns='urn:schemas-microsoft-com:xml-analysis'

		bso->xwrite_raw(TOK_ENVELOPE.ptr(), TOK_ENVELOPE.len());
		//if( _sessionid != -1 ) {
			bso->xwrite_raw(TOK_HEADER_BEGIN.ptr(), TOK_HEADER_BEGIN.len());
			bso->xwrite_raw(TOK_SESSION.ptr(), TOK_SESSION.len());
			bso->xwrite_raw(TOK_HEADER_END.ptr(), TOK_HEADER_END.len());
		//}
		bso->xwrite_raw(TOK_BODY.ptr(), TOK_BODY.len());

		return 0;
	}

	opcd end_soap_envelope()
	{
		return 0;
	}

	static void add_soap_begin(coid::binstream & bs)
	{
/*		static const coid::token TOK_ENVELOPE = "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>";
		static const coid::token TOK_BODY = "<soap:Body>";
		static const coid::token TOK_HEADER_BEGIN = "<soap:Header>";
		static const coid::token TOK_HEADER_END = "</soap:Header>";
		static const coid::token TOK_SESSION = "<Session>";
		static const coid::token TOK_SESSION_END = "</Session>";

		bs.xwrite_raw(TOK_ENVELOPE.ptr(), TOK_ENVELOPE.len());
//		if( _sessionid != -1 ) {
//		}
		bs.xwrite_raw(TOK_BODY.ptr(), TOK_BODY.len());*/
	}

	static void add_soap_end(coid::binstream & bs)
	{
		static const coid::token t = "</soap:Body></soap:Envelope>";
		bs.xwrite_raw(t.ptr(), t.len());
	}


public:
	///
	fmtsoapxml( binstream& b ) 
		: fmtstreamxml2( b )
		, _sessionid(-1) {}

	///Return method name parsed in parse_soap_header
	const coid::charstr & method_name() const { return _method_name; }

	///Override to parse custom header
	opcd parse_soap_header()
	{
		_tokenizer.match('<');
		token tok=_tokenizer.next();
		tattr_map map;
		int flags;
		charstr tmp;

		static const coid::token BEGIN_SESSION="BeginSession";
		static const coid::token END_SESSION="EndSession";
		static const coid::token SESSION="Session";
		static const coid::token ENV_ENVELOPE="SOAP-ENV:Envelope";
		static const coid::token SOAP_ENVELOPE="soap:Envelope";

		if( tok=="?") {
			//_tokenizer.match('?');
			_tokenizer.match("xml");
			_tokenizer.match("version");
			_tokenizer.match('=');
			match_val("1.0");
			_tokenizer.match("encoding");
			_tokenizer.match('=');
			match_val("UTF-8");
			_tokenizer.match('?');
			_tokenizer.match('>');
			_tokenizer.match('<');
			tok=_tokenizer.next();
		}
		if( tok==ENV_ENVELOPE ) {
			_keytbl=0;
		} else if( tok==SOAP_ENVELOPE ) {
			_keytbl=1;
		} else
			_keytbl=-1;

		DASSERT(_keytbl!=-1 && "Unknown SOAP key or structure!");

		parse_attributes(map);
		find_end_of_key();

/*		{
			tattr_map map;
			parse_attributes(map);
			coid::charstr * val = map.find_value(keytable(_keytbl,XMLNS));
			if (!val || (*val) != "http://schemas.xmlsoap.org/soap/envelope/") {
				_tokenizer.prepare_exception() << "error parsing SOAP envelope";
				throw _tokenizer.final_exception();
			}
			val = map.find_value("SOAP-ENV:encodingStyle");
			if (!val || (*val) != "http://schemas.xmlsoap.org/soap/encoding/") {
				_tokenizer.prepare_exception() << "error parsing SOAP envelope";
				throw _tokenizer.final_exception();
			}
		}*/

		//!TODO check for all possible keys...
		//while( get_simple_key()!=keytable(_keytbl,BODY) );

		get_key(tmp,map,flags);

		if( tmp==keytable(_keytbl,HEADER) ) {
			get_key(tmp,map,flags);
			/* FIXME: unused variables
			if( tok==BEGIN_SESSION ) {
				int a=0;

			} else if( tok==END_SESSION ) {
				int a=0;

			} else if( tok==SESSION ) {
				int a=0;

			}*/
		} else {
		}


		while( tmp!=keytable(_keytbl,BODY) && flags!=CLOSING_KEY ) { get_key(tmp,map,flags); }

		get_key(tmp,map,flags);

		// parse method name, namespace etc.
		//_tokenizer.match('<');
		_method_name = tmp;
/*		
		{
			tattr_map map;
			parse_attributes(map);
			coid::charstr * val = map.find_value("xmlns");
			if (!val || (*val) != "urn:schemas-microsoft-com:xml-analysis") {
				_tokenizer.prepare_exception() << "error parsing SOAP method";
				throw _tokenizer.final_exception();
			}

			val = map.find_value("SOAP-ENV:encodingStyle");
			if (!val || (*val) != "http://schemas.xmlsoap.org/soap/encoding/") {
				_tokenizer.prepare_exception() << "error parsing SOAP method";
				throw _tokenizer.final_exception();
			}

		}
*/
		//_tokenizer.match('>');
		return 0;
	}

    void createErrorMessage(const coid::token desc)
	{
		coid::charstr str;

		//str << "<?xml version='1.0'?>";
		str << "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'><soap:Body>";
		str << "<soap:Fault xmlns='http://schemas.xmlsoap.org/soap/envelope/'>"
			"<faultcode>XMLAnalysisError.0x80000005</faultcode>"
			"<faultstring>";
		if( !_tokenizer.synthesize_string( lexstr, desc, str) )
			str += desc;
		str << "</faultstring><detail><Error Description='";
		if( !_tokenizer.synthesize_string( lexstr, desc, str) )
			str += desc;
		str << "' ErrorCode='0x800001' HelpFile='' Source='MOLe'/></detail>";
		str << "</soap:Fault>";
		str << "</soap:Body></soap:Envelope>";
		_binw->reset_write();
		xwrite_raw(str.ptr(), str.len());
	}
};

#endif // __COIDSERVER_FMTSOAPXML_H__
